//
// Created by songgf on 25-3-20.
//

#include "bdf_to_inp_convertor.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <cerrno>
#include <cstring>
#include <iomanip>
#include <unordered_map>
#include <set>

BdfToInpConvert::BdfToInpConvert()
{
}

BdfToInpConvert::~BdfToInpConvert()
{
}

int BdfToInpConvert::convert(const std::string &bdfPath, const std::string &inpPath)
{
    int parseResult = parseBdfFile(bdfPath);
    if (parseResult != 0)
    {
        return parseResult;
    }
    return generateInpFile(inpPath);
}

int BdfToInpConvert::parseBdfFile(const std::string &path)
{
    std::ifstream file(path);
    if (!file)
    {
        std::cerr << "Error: Cannot open file!" << std::endl;
        std::cerr << "Reason: " << strerror(errno) << "," << path << std::endl;
        return -1;
    }

    std::string line;

    while (std::getline(file, line))
    {
        trim(line);
        if (line.empty() || line[0] == '$')
            continue;

        if (line.rfind("GRID", 0) == 0)
        {
            parseNode(line);
        }
        else if (line.rfind("RBE2", 0) == 0)
        {
            std::string fullLine = line;
            while (lineHasContinuation(fullLine))
            {
                std::string cont;
                if (!getline(file, cont))
                    break;
                trim(cont);
                std::string dataPart;
                if (cont.size() >= 8 && cont[0] == '+')
                {
                    std::string maybeHex = cont.substr(1, 7);
                    bool isHex = std::all_of(maybeHex.begin(), maybeHex.end(), [](char c)
                                             { return std::isxdigit(c); });
                    if (isHex)
                    {
                        dataPart = cont.substr(8);
                    }
                    else
                    {
                        dataPart = cont;
                    }
                    fullLine += ' ' + dataPart;
                }
            }

            std::replace(fullLine.begin(), fullLine.end(), ',', ' ');
            parseRBE2(fullLine);
        }
        else if (line.rfind("CTRIA3") == 0 || line.rfind("CQUAD4") == 0)
        {
            parseElement(line);
        }
        else if (line.rfind("MAT1", 0) == 0)
        {
            parseMat1(line);
        }
        else if (line.rfind("PSHELL", 0) == 0)
        {
            parsePshell(line);
        }
    }

    file.close();
    return 0;
}

void BdfToInpConvert::parseNode(const std::string &line)
{
    if (line.rfind("GRID", 0) != 0)
        return;

    try
    {
        Node node;
        node.id = std::stoi(line.substr(8, 8));
        node.x = std::stod(line.substr(24, 8));
        node.y = std::stod(line.substr(32, 8));
        node.z = std::stod(line.substr(40, 8));

        nodes.push_back(node);
    }
    catch (const std::exception &e)
    {
        std::cerr << "GRID parse error: " << e.what()
                  << " | line: '" << line << "'\n";
    }
}

void BdfToInpConvert::parseElement(const std::string &line)
{
    std::istringstream iss(line);
    std::string token;
    std::vector<std::string> tokens;

    while (iss >> token)
    {
        tokens.push_back(token);
    }

    if (tokens.size() < 4)
    {
        std::cerr << "Invalid element line: " << std::endl;
        return;
    }

    std::string typeStr = tokens[0];
    int expectedNodes = 0;

    if (typeStr == "CTRIA3")
        expectedNodes = 3;
    if (typeStr == "CQUAD4")
        expectedNodes = 4;

    if (tokens.size() < 3 + expectedNodes)
    {
        std::cerr << "Mismatch in node count for " << typeStr << ":" << line << std::endl;
    }

    Element element;
    element.type = mapBdfElementTypeToInp(typeStr);

    try
    {
        element.id = std::stoi(tokens[1]);
        element.pid = std::stoi(tokens[2]);

        for (int i = 3; i < tokens.size(); i++)
        {
            element.nodes.push_back(std::stoi(tokens[i]));
        }
    }
    catch (const std::exception &e)
    {
        std::cerr << "Conversion error: " << e.what() << " in line: " << line << std::endl;
        return;
    }

    elements.push_back(std::move(element));
}

std::string
BdfToInpConvert::mapBdfElementTypeToInp(const std::string &bdfType)
{
    static std::map<std::string, std::string> typeMap = {
        // 四节点壳单元
        {"CQUAD4", "S4"},
        {"CTRIA6", "S6"},
        {"CTRIA3", "S3"},
        {"CQUAD8", "S8"}};
    auto it = typeMap.find(bdfType);
    return it != typeMap.end() ? it->second : "UNKNOWN";
}

int BdfToInpConvert::generateInpFile(const std::string &path)
{
    std::ofstream file(path);
    if (!file.is_open())
    {
        std::cerr << "Error opening INP file: " << std::endl;
        return -1;
    }

    file << "*NODE\n";
    for (const auto &node : nodes)
    {
        double x = node.x * 1000;
        double y = node.y * 1000;
        double z = node.z * 1000;
        file << std::right << std::setw(7) << node.id << std::fixed << std::setprecision(3) << ", " << std::setw(12) << x << ", " << std::setw(12) << y << ", " << std::setw(12) << z
             << "\n";
    }

    if (elements.empty())
    {
        std::cerr << "Error: No elements found before writing INP." << std::endl;
        return -1;
    }

    std::unordered_map<std::string, std::vector<Element>> elementGroup;
    for (const auto &elem : elements)
    {
        elementGroup[elem.type].push_back(elem);
    }

    for (const auto &[type, group] : elementGroup)
    {
        file << "*ELEMENT, TYPE=" << type << "\n";
        for (const auto &element : group)
        {
            file << element.id;
            for (int nodeID : element.nodes)
            {
                file << ", " << nodeID;
            }
            file << "\n";
        }
    }

    std::map<int, std::vector<int>> pidToElemIds;
    std::map<int, int> pidToIndex;

    int elsetIndex = 1;
    for (const auto &elem : elements)
    {
        pidToElemIds[elem.pid].push_back(elem.id);
    }

    for (const auto &[pid, elemIds] : pidToElemIds)
    {
        int currentIndex = elsetIndex++;
        pidToIndex[pid] = currentIndex;
        std::string elsetName = "E" + std::to_string(currentIndex);

        file << "*Elset,elset=" << elsetName << "\n";
        for (size_t i = 0; i < elemIds.size(); i++)
        {
            file << elemIds[i];
            if (i % 16 == 15 || i == elemIds.size() - 1)
            {
                file << "\n";
            }
            else
            {
                file << ",";
            }
        }
    }

    file << "\n** SECTIONS\n";
    for (const auto &[pid, index] : pidToIndex)
    {
        auto psIt = pidToPshell.find(pid);
        if (psIt == pidToPshell.end())
            continue;

        int mid = psIt->second.mid1;
        double thickness = psIt->second.thickness;

        std::string elsetName = "ES" + std::to_string(index);
        std::string matName = "MATERIAL-" + std::to_string(mid);
        std::string sectionName = "Section-1–" + elsetName;

        std::cout << "sectionName: " << sectionName << std::endl;
        file << "** Section: " << sectionName << "\n";
        file << "*Shell Section, elset=" << elsetName << ", material=" << matName << "\n";
        file << std::fixed << std::setprecision(1) << thickness << ", 3\n";

    
        std::set<int> writtenMids;
        for (const auto &[pid, index] : pidToIndex)
        {
            auto psIt = pidToPshell.find(pid);
            if (psIt == pidToPshell.end())
            {
                continue;
            }

            int mid = psIt->second.mid1;
            if (writtenMids.count(mid))
            {
                continue;
            }

            std::string matName = "MATERIAL-" + std::to_string(mid);

            file << "*Material, name=" << matName << "\n";
            file << "*Conductivity\n60.5,\n";
            file << "*Density\n7.85e-09,\n";
            file << "*Elastic\n200000., 0.3\n";
            file << "*Expansion\n1.2e-05,\n";
            file << "*Specific Heat\n4.34e+08,\n";
        }
    }
    file.close();
    return 0;
}

void BdfToInpConvert::trim(std::string &s)
{
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch)
                                    { return !std::isspace(ch); }));
    s.erase(std::find_if(s.rbegin(), s.rend(),
                         [](unsigned char ch)
                         { return !std::isspace(ch); })
                .base(),
            s.end());
}

std::vector<std::string> BdfToInpConvert::split(const std::string &s,
                                                char delimiter)
{
    std::vector<std::string> tokens;
    std::stringstream ss(s);
    std::string token;

    while (std::getline(ss, token, delimiter))
    {
        trim(token);
        if (!token.empty())
            tokens.push_back(token);
    }
    return tokens;
}

void BdfToInpConvert::parseRBE2(const std::string &full)
{
    std::vector<std::string> fields;
    std::istringstream iss(full);
    std::string w;

    while (iss >> w)
    {
        if (w[0] == '+')
            continue;

        auto plusPos = w.find('+');
        if (plusPos != std::string::npos)
        {
            fields.push_back(w.substr(0, plusPos));
            continue;
        }
        fields.push_back(w);
    }

    if (fields.size() < 4)
    {
        std::cerr << "Bad RBE2: " << full << '\n';
        return;
    }

    RBE2 r;
    try
    {
        r.id = std::stoi(fields[1]);
        r.independentNode = std::stoi(fields[2]);
        r.dof = fields[3];
        for (size_t i = 4; i < fields.size(); ++i)
            r.dependentNodes.push_back(std::stoi(fields[i]));
    }
    catch (const std::exception &e)
    {
        std::cerr << "RBE2 parse error: " << e.what() << '\n';
        return;
    }
    rbe2Elements.push_back(std::move(r));
}
bool BdfToInpConvert::lineHasContinuation(const std::string &line)
{
    auto pos = line.find('+');
    if (pos == std::string::npos)
        return false;
    return std::all_of(line.begin() + pos + 1, line.end(), [](unsigned char c)
                       { return std::isxdigit(c); });
}

void BdfToInpConvert::parseMat1(const std::string &line)
{
    std::istringstream iss(line);
    std::vector<std::string> tokens;
    std::string token;
    while (iss >> token)
    {
        tokens.push_back(token);
    }

    try
    {
        Material mat;
        mat.mid = std::stoi(tokens[1]);
        mat.E = std::stoi(tokens[2]);
        mat.NU = std::stoi(tokens[4]);

        if (tokens.size() >= 6)
            mat.RHO = std::stoi(tokens[5]);
        midToMaterial[mat.mid] = mat;
    }
    catch (...)
    {
        std::cerr << "Failed to parse MAT1 line: " << line << "\n";
    }
}

void BdfToInpConvert::parsePshell(const std::string &line)
{
    std::istringstream iss(line);
    std::vector<std::string> tokens;
    std::string token;
    while (iss >> token)
    {
        tokens.push_back(token);
    }

    if (tokens.size() < 4)
    {
        std::cerr << "PSHELL parse error: not enough tokens in line:\n"
                  << line << "\n";
        return;
    }

    try
    {
        PShell ps;

        const std::string &combined = tokens[1];
        if (combined.size() > 12)
        {
            ps.pid = std::stoi(combined.substr(0, 4));
            ps.mid1 = std::stoi(combined.substr(4));
        }

        ps.thickness = (combined.length() >= 12) ? std::stod(tokens[2]) : std::stod(tokens[3]);
        pidToPshell[ps.pid] = ps;
        std::cout << "[PSHELL] Parsed pid = " << ps.pid << ", mid1 = " << ps.mid1 << ", t = " << ps.thickness << "\n";
    }
    catch (const std::exception &e)
    {
        std::cerr << "PSHELL parse exception: " << e.what() << "\nLine: " << line << "\n";
    }
}
